package com.cloudlead.plugin.security.service.impl;

import com.cloudlead.common.lang.Messages;
import com.cloudlead.common.lang.api.RequestHeaderUtils;
import com.cloudlead.common.lang.exception.RequestNotAcceptableException;
import com.cloudlead.common.lang.exception.ResourceNotFoundException;
import com.cloudlead.common.lang.exception.UnauthorizedException;
import com.cloudlead.common.lang.persistence.Criteria;
import com.cloudlead.common.lang.persistence.Term;
import com.cloudlead.common.lang.persistence.Filter;
import com.cloudlead.common.lang.security.LoginSource;
import com.cloudlead.common.lang.security.Principal;
import com.cloudlead.common.lang.security.SecurityUtils;
import com.cloudlead.common.lang.security.Subject;
import com.cloudlead.common.lang.service.AbstractBaseService;
import com.cloudlead.common.worker.security.PasswordHelper;
import com.cloudlead.common.worker.security.ScAuth;
import com.cloudlead.common.worker.security.TokenInfoService;
import com.cloudlead.plugin.security.BusinessModules;
import com.cloudlead.plugin.security.service.*;
import com.cloudlead.plugin.security.utils.VerifyCodeUtils;
import com.cloudlead.plugin.security.vo.ScUser;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;

import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by dean on 2015/8/8.
 */
@Service("authorizationService")
public class AuthorizationServiceImpl extends AbstractBaseService implements AuthorizationService {
    private static final int MAX_RETRY_COUNT = 1000;

    @Inject
    private CodeService codeService;

    @Inject
    private ScUserService scUserService;

    @Inject
    private UserInfoService userService;

    @Inject
    private TokenService tokenService;

    @Inject
    private TokenInfoService tokenInfoService;

    @Inject
    private PasswordRetryService passwordRetryService;

    public AuthorizationServiceImpl() {
        super(BusinessModules.Authorization);
    }

    @Transactional
    @Override
    public ScAuth getToken(String username, String password, LoginSource source, String group, String code) {
        if (LoginSource.web.equals(source)) {
            if (StringUtils.isBlank(code)) {
                throw new RequestNotAcceptableException(Messages.getMessage("security.authorization.code.NotNull"));
            }
            String verify = codeService.getCode(RequestHeaderUtils.getRequestId());
            if (!code.equalsIgnoreCase(verify)) {
                throw new RequestNotAcceptableException(Messages.getMessage("security.authorization.code.NotCorrect"));
            }
        }
        AtomicInteger passwordRetry = passwordRetryService.getPasswordRetry(username);
        if (null == passwordRetry) {
            passwordRetry = new AtomicInteger(0);
        }
        if (MAX_RETRY_COUNT <= passwordRetry.get()) {
            throw new UnauthorizedException(Messages.getMessage("security.authorization.password.retry.error"));
        }
        // 可以用用户名、手机号码或者邮件地址登录
        Filter filter = new Filter();
        filter.or(new Criteria(new Term("username", username)));
        filter.or(new Criteria(new Term("mobile", username)));
        filter.or(new Criteria(new Term("email", username)));
        ScUser findOne = scUserService.findOne(filter);
        if (null == findOne) {
            throw new UnauthorizedException(Messages.getMessage("security.authorization.user.NotFound"));
        } else if (null != findOne.getLocked() && findOne.getLocked()) {
            throw new UnauthorizedException(Messages.getMessage("security.authorization.user.locked"));
        } else if (StringUtils.isBlank(findOne.getPassword()) || !validator(findOne, password)) {
            int passwordRetryCount = passwordRetry.incrementAndGet();
            passwordRetryService.addPasswordRetry(username, passwordRetry);
            throw new UnauthorizedException(Messages.getMessage("security.authorization.password.retry.over", passwordRetryCount, (MAX_RETRY_COUNT - passwordRetryCount)));
        } else {
            passwordRetryService.removePasswordRetry(username);

            username = findOne.getUsername();
            String accessToken = tokenInfoService.getAccessTokenByUser(username);
            if (StringUtils.isBlank(accessToken)) {
                accessToken = UUID.randomUUID().toString();
            }

            //如果用户使用了不同的身份登录，则更新默认用户身份
            if (StringUtils.isNotBlank(group) && !group.equals(findOne.getDefaultGroup())) {
                findOne.setDefaultGroup(group);
                scUserService.update(findOne);
            }

            //使用默认用户组登录
            if (StringUtils.isBlank(group) && StringUtils.isNotBlank(findOne.getDefaultGroup())) {
                group = findOne.getDefaultGroup();
            }

            Subject subject = new Subject();

            Principal principal = userService.findByUsername(username);
            subject.setPrincipal(principal);
            subject.setSource(source);
            subject.setCurrentGroup(group);
            subject.setRoles(scUserService.findRoles(username, group));
            subject.setPrivileges(scUserService.findPrivileges(username, group));
            subject.setGroups(scUserService.findGroups(username));

            ScAuth scAuth = new ScAuth(accessToken, TokenService.ACCESS_TOKEN_EXP_SECONDS, subject);
            tokenService.addSubject(accessToken, subject);
            tokenService.addAccessToken(username, accessToken);
            return scAuth;
        }
    }

    @Transactional
    @Override
    public void changeGroup(String group) {
        Subject subject = SecurityUtils.getSubject();
        if (null == subject) {
            throw new UnauthorizedException(Messages.getMessage("security.authorization.token.required"));
        }

        ScUser example = new ScUser();
        String username = subject.getPrincipal().getUsername();
        example.setUsername(username);
        ScUser scUser = scUserService.findOne(example);
        scUser.setDefaultGroup(group);
        scUserService.update(scUser);

        subject.setCurrentGroup(group);
        subject.setRoles(scUserService.findRoles(username, group));
        subject.setPrivileges(scUserService.findPrivileges(username, group));

        tokenService.addSubject(SecurityUtils.getAccessToken(), subject);

    }

    @Override
    public void removeToken() {
        tokenService.removeToken(RequestHeaderUtils.getAccessToken());
    }

    @Override
    public void forgetPassword(String mobile, String code, String password) {
        if (!code.equals(codeService.getCode(mobile))) {
            throw new RequestNotAcceptableException(Messages.getMessage("security.authorization.code.NotCorrect"));
        }

        Principal principal = userService.findByMobile(mobile);
        ScUser example = new ScUser();
        example.setUsername(principal.getUsername());
        ScUser scUser = scUserService.findOne(example);
        if (null == scUser) {
            throw new ResourceNotFoundException(Messages.getMessage("security.authorization.user.NotFound"));
        }
        scUser.setPassword(PasswordHelper.encryption(password, scUser.getSalt()));
        scUserService.update(scUser);

        String accessToken = tokenInfoService.getAccessTokenByUser(example.getUsername());
        if (StringUtils.isBlank(accessToken)) {
            accessToken = tokenInfoService.getAccessTokenByUser(example.getMobile());
        }
        if (StringUtils.isBlank(accessToken)) {
            accessToken = tokenInfoService.getAccessTokenByUser(example.getEmail());
        }
        Subject subject = tokenInfoService.getSubjectByAccessToken(accessToken);
        if (null == subject) {
            passwordRetryService.removePasswordRetry(mobile);
        } else {
            passwordRetryService.removePasswordRetry(principal.getUsername());
        }
        tokenService.removeToken(accessToken);
    }

    @Override
    public String getVerifyCode(String mobile) {
        String code = codeService.getCode(mobile);
        if (null == code) {
            code = VerifyCodeUtils.generateNumberVerifyCode(6);
        }
        if (codeService.hasRetry(mobile)) {
            codeService.addCode(mobile, code);
            codeService.sendCode(mobile, code);
        }
        return code;
    }

    private boolean validator(ScUser user, String password) {
        return user.getPassword().equals(PasswordHelper.encryption(password, user.getSalt()));
    }
}
